
import json
import csv

import pandas as pd

from my_const import *
from fund.strategy.strategy_by_ma import strategy_based_on_moving_average
from fund.strategy.strategy_by_drop import strategy_based_on_drop
from util.csv_util import csv_2_df
from fund.get_history_data import read_funds_from_yml_list
from util.date_util import *

from util.log_util import get_the_logger
logger = get_the_logger(__name__)

pd.set_option('future.no_silent_downcasting', True)


# ---------------------- 保存走势+买入点到CSV ----------------------
def save_fund_chart_data(fund_code, strategy_name, fund_df, buy_records, sd, ed, ma_range=None):
    """
    保存基金走势和买入点数据到CSV
    :param fund_code: 基金代码
    :param strategy_name: 策略名（ma/drop）
    :param fund_df: 基金原始净值数据（DataFrame）
    :param buy_records: 策略生成的买入记录（list，来自strategy_result['买入记录']）
    :param sd: 策略开始日期（字符串，如'2022-10-01'）
    :param ed: 策略结束日期（字符串，如'2024-10-01'）
    :param ma_range: MA策略的均线天数（如30，drop策略为None）
    """

    # 构造文件名（区分策略+MA天数，避免重复）
    if strategy_name == "ma" and ma_range:
        csv_filename = f"{fund_code}_{strategy_name}_{ma_range}d.csv"
    else:
        csv_filename = f"{fund_code}_{strategy_name}.csv"
    csv_path = os.path.join(BACKTEST_DIR, csv_filename)

    # 筛选策略时间范围内的净值走势数据（sd ~ ed）
    # 转换日期格式，确保和df_fund的索引匹配（df_fund索引是datetime类型）
    sd_dt = pd.to_datetime(sd)
    ed_dt = pd.to_datetime(ed)
    df_trend = fund_df.loc[sd_dt:ed_dt].copy()  # 策略时间段内的净值数据
    df_trend.reset_index(inplace=True)  # 把日期索引转为普通列（方便后续合并）
    df_trend.rename(columns={"净值日期": "date", "单位净值": "net_value"}, inplace=True)  # 统一列名

    # 处理买入记录：转为DataFrame，方便和走势数据合并
    if buy_records:
        df_buy = pd.DataFrame(buy_records)
        # 统一日期格式（确保和df_trend的date列类型一致）
        df_buy["date"] = pd.to_datetime(df_buy["date"])
        # 保留买入核心字段（标记是否买入+关键信息）
        df_buy = df_buy[["date", "price", "cost", "units"]].rename(
            columns={"price": "buy_price", "cost": "buy_cost", "units": "buy_units"}
        )
        df_buy["is_buy"] = True  # 新增“是否买入”标记，方便后续提取买入点
    else:
        # 无买入记录：创建空DataFrame，避免合并报错
        df_buy = pd.DataFrame(columns=["date", "buy_price", "buy_cost", "buy_units", "is_buy"])

    # 合并走势数据和买入数据（用日期关联，无买入的日期填充为False/NaN）
    df_combined = pd.merge(
        df_trend[["date", "net_value"]],  # 只保留走势核心字段
        df_buy,
        on="date",
        how="left"  # 左连接：保留所有走势日期，买入日期对应填充数据
    )

    # 填充无买入的行：is_buy设为False，其他买入字段设为0（避免NaN）
    df_combined["is_buy"] = df_combined["is_buy"].fillna(False)
    df_combined[["buy_price", "buy_cost", "buy_units"]] = df_combined[
        ["buy_price", "buy_cost", "buy_units"]].fillna(0)

    # 保存为CSV（编码UTF-8，避免中文乱码）
    df_combined.to_csv(csv_path, index=False, encoding="utf-8-sig")
    logger.info(f"Saved fund {fund_code} buy point to：{csv_path}")



def save_evaluation_result(results, strategy_name, days=None):
    """
    保存评估结果
    :param results: 评估结果列表，每个元素包含基金代码、名称、是否建议买入等信息
    :param days: 均值计算天数（如30、60），用于MA策略
    :param strategy_name: 策略名称（如'drop'），用于非MA策略
    """
    today_str = today_yyyymmdd()
    
    # 构造文件名
    if not strategy_name:
        raise ValueError("no strategy name")

    if strategy_name == "ma":
        json_filename = f'result_{today_str}_{strategy_name}_{days}d.json'
    else:
        json_filename = f'result_{today_str}_{strategy_name}.json'
    
    # 1. 保存为JSON（供Web读取）
    json_path = os.path.join(EVALUATION_RESULT_DIR, json_filename)
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump(results, f, ensure_ascii=False, indent=2)

    # 2. 保存为CSV（供归档）
    csv_path = os.path.join(EVALUATION_RESULT_DIR, f'archive_{today_str}.csv')
    # 若文件不存在，先写入表头
    write_header = not os.path.exists(csv_path)
    with open(csv_path, 'a', encoding='utf-8', newline='') as f:
        writer = csv.writer(f)
        if write_header:
            writer.writerow(['策略', '天数', '基金代码', '基金名称', '是否建议买入', '年化收益率(%)', '买入次数'])
        for item in results:
            writer.writerow([
                strategy_name,
                days,
                item['fund_code'],
                item['fund_name'],
                item['should_buy'],
                item['annual_return'],
                item['buy_count']
            ])



def evaluate_fund_from_yml_list(yml_list, days_list, sd, ed, is_today=True):
    period = 'daily'
    fund_code_list, m = read_funds_from_yml_list(yml_list)
    workday_cnt = workday_of_range(sd, ed)
    logger.info(f'time range: {sd} {ed}')

    all_results = []  # 收集所有天数的评估结果，用于返回

    # 1. 执行MA策略评估
    for days in days_list:
        res_list = []  # 当天数的评估结果
        res_good_list = []
        res_bad_list = []

        for fund_code in fund_code_list:
            csv_file = os.path.join(HISTORY_DIR, f'{fund_code}-{period}.csv')
            df = csv_2_df(csv_file)
            if df.empty:
                logger.info(f"基金 {fund_code} 无数据，跳过评估")
                continue

            # 执行策略评估
            benefit = strategy_based_on_moving_average(df, 0, 100, days, sd, ed)
            # 判断是否建议买入
            should_buy = if_should_buy(benefit, is_today)

            # ---------------------- 新增：保存MA策略的走势+买入点 ----------------------
            if days == 60:
                save_fund_chart_data(
                    fund_code=fund_code,
                    strategy_name="ma",
                    fund_df=df,  # 基金原始净值数据
                    buy_records=benefit["买入记录"],  # 策略生成的买入记录
                    sd=sd,  # 策略开始日期
                    ed=ed,  # 策略结束日期
                    ma_range=days  # MA均线天数（30/60/90）
                )


            # 整理结果字典
            fund_name = m.get(fund_code, '未知名称')
            result_item = {
                'fund_code': fund_code,
                'fund_name': fund_name,
                'days': days,
                'should_buy': should_buy,
                'annual_return': round(benefit['年化收益率'] * 100, 2),  # 转为百分比
                'buy_count': f"{benefit['买入次数']}/{workday_cnt}",
                'estimate_date': benefit['结束时间']
            }
            res_list.append(result_item)

            # 控制台输出（保持原有逻辑）
            s = (
                f'{fund_code} {fund_name:<20} Buy?={should_buy}, '
                f'年化收益率={result_item["annual_return"]:.02f}%, '
                f'买入次数={result_item["buy_count"]}'
                , benefit['年化收益率'])
            if should_buy:
                res_good_list.append(s)
            else:
                res_bad_list.append(s)

        # 保存当天数的评估结果
        save_evaluation_result(res_list, "ma", days=days)
        all_results.extend(res_list)

        # 控制台输出排序后的结果
        logger.info(f">>>>> mean {days} days {len(res_good_list)}/{len(fund_code_list)} >>>>>")
        res_good_list.sort(key=lambda x: x[1], reverse=True)
        for i in res_good_list:
            logger.info(i[0])
        logger.info("<" * 20)

    # 2. 执行跌幅策略评估
    drop_res_list = []
    drop_res_good_list = []
    drop_res_bad_list = []
    
    for fund_code in fund_code_list:
        csv_file = os.path.join(HISTORY_DIR, f'{fund_code}-{period}.csv')
        df = csv_2_df(csv_file)
        if df.empty:
            logger.info(f"Fund {fund_code} has no data, skip evaluation by drop")
            continue

        # 执行跌幅策略评估
        benefit = strategy_based_on_drop(df, 200, sd, ed)

        # 检查买入记录中的日期是否是今天
        should_buy = if_should_buy(benefit, is_today)

        save_fund_chart_data(
            fund_code=fund_code,
            strategy_name="drop",
            fund_df=df,  # 基金原始净值数据
            buy_records=benefit["买入记录"],  # 策略生成的买入记录
            sd=sd,  # 策略开始日期
            ed=ed,  # 策略结束日期
        )

        # 整理结果字典
        fund_name = m.get(fund_code, '未知名称')
        result_item = {
            'fund_code': fund_code,
            'fund_name': fund_name,
            'strategy': 'drop',
            'should_buy': should_buy,
            'annual_return': round(benefit['年化收益率'] * 100, 2),  # 转为百分比
            'buy_count': f"{benefit['买入次数']}/{workday_cnt}",
            'estimate_date': benefit['结束时间']
        }
        drop_res_list.append(result_item)

        # 控制台输出
        s = (
            f'{fund_code} {fund_name:<20} Buy?={should_buy}, '
            f'年化收益率={result_item["annual_return"]:.02f}%, '
            f'买入次数={result_item["buy_count"]}'
            , benefit['年化收益率'])
        if should_buy:
            drop_res_good_list.append(s)
        else:
            drop_res_bad_list.append(s)

    # 保存跌幅策略评估结果
    save_evaluation_result(drop_res_list, strategy_name='drop')
    all_results.extend(drop_res_list)

    # 控制台输出跌幅策略结果
    logger.info(f">>>>> drop strategy {len(drop_res_good_list)}/{len(fund_code_list)} >>>>>")
    drop_res_good_list.sort(key=lambda x: x[1], reverse=True)
    for i in drop_res_good_list:
        logger.info(i[0])
    logger.info("<" * 20)

    return all_results  # 返回所有结果，供Web调用


def if_should_buy(benefit, is_today=True):
    if is_today:
        today = datetime.now().date()
    else:
        today = y_m_d_to_date(benefit['结束时间'])

    record = benefit['买入记录']

    if not record:
        return False
    
    return record[-1]['date'].date() == today


def evaluate_today_fund(is_today=True):
    # sd = before_1y_yyyymmdd()
    sd = before_2y_yyyymmdd()
    # sd = before_6m_yyyymmdd()
    # sd = before_3y_yyyymmdd()

    ed = today_yyyymmdd()
    # ed = before_6m_yyyymmdd()
    # ed = before_1y_yyyymmdd()

    evaluate_fund_from_yml_list(MY_PREFER_FUND_YML_LIST, MA_DAYS_LIST, sd, ed, is_today)
    # evaluate_fund_from_yml_list(TEST_SINGLE_YML_LIST, MEAN_DAYS_LIST, sd, ed, is_today)


if __name__ == '__main__':
    # evaluate_today_fund()
    evaluate_today_fund(is_today=False)
